#include "c-utils/internal/net.h"

#include <arpa/inet.h>
#include <errno.h>
#include <ifaddrs.h>
#include <linux/if_packet.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/**
 * 根据网卡名称获取该网卡信息
 */
utils_if_info utils_net_if_info(const char *if_name)
{
    utils_if_info info = {0};
    struct ifaddrs *ifAddrStruct = NULL, *ifa = NULL;
    getifaddrs(&ifAddrStruct);
    if (NULL == ifAddrStruct)
        return info;
    for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next)
    {
        if (strcmp(ifa->ifa_name, if_name))
            continue;

        strcpy(info.if_name, ifa->ifa_name);
        info.if_idx = if_nametoindex(ifa->ifa_name);

        if (info.speed == 0)
        {
            struct ifreq ifr;
            struct ethtool_cmd cmd;
            strcpy(ifr.ifr_name, info.if_name);
            ifr.ifr_data = (void *)&cmd;
            cmd.cmd = ETHTOOL_GSET;
            int fd = socket(AF_INET, SOCK_STREAM, 0);
            int result;
            if (ioctl(fd, SIOCETHTOOL, &ifr) >= 0)
            {
                info.speed = ethtool_cmd_speed(&cmd);
                switch (cmd.duplex)
                {
                case DUPLEX_HALF:
                    info.duplex = 0;
                    break;
                case DUPLEX_FULL:
                    info.duplex = 1;
                    break;
                default:
                    if (cmd.duplex != 0xFF)
                    {
                        fprintf(stderr, "%s: Unknown mode (0x%x).\n", info.if_name, cmd.duplex);
                        fflush(stderr);
                    }
                }
            }
            if (0 < fd)
                do
                {
                    result = close(fd);
                } while (result == -1 && errno == EINTR);
        }
        if (ifa->ifa_addr == NULL)
            continue;
        switch (ifa->ifa_addr->sa_family)
        {
        case AF_INET:
            inet_ntop(AF_INET, &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr, info.ipv4.addr, INET_ADDRSTRLEN);
            inet_ntop(AF_INET, &((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr, info.ipv4.mask, INET_ADDRSTRLEN);
            break;
        case AF_INET6:
            inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr, info.ipv6.addr, INET6_ADDRSTRLEN);
            inet_ntop(AF_INET6, &((struct sockaddr_in6 *)ifa->ifa_netmask)->sin6_addr, info.ipv6.mask, INET6_ADDRSTRLEN);
            break;
        case AF_PACKET:
        {
            struct sockaddr_ll *sall = (struct sockaddr_ll *)ifa->ifa_addr;
            memcpy(info.if_mac_hex, sall->sll_addr, sall->sll_halen > 8 ? 8 : sall->sll_halen);
            sprintf(info.if_mac, "%02x:%02x:%02x:%02x:%02x:%02x",
                    sall->sll_addr[0],
                    sall->sll_addr[1],
                    sall->sll_addr[2],
                    sall->sll_addr[3],
                    sall->sll_addr[4],
                    sall->sll_addr[5]);
        }
        break;
        default:
            break;
        }
    }
    freeifaddrs(ifAddrStruct);
    return info;
}

/**
 * 根据请求的 IP 获取入口网卡的信息
 */
utils_if_info utils_net_if_info_by_local_ip(in_addr_t compare_ip_addr)
{
    utils_if_info info = {0};
    struct ifaddrs *ifAddrStruct = NULL, *ifa = NULL;
    getifaddrs(&ifAddrStruct);
    if (NULL == ifAddrStruct)
        return info;
    for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next)
    {
        if (NULL == ifa->ifa_addr)
            continue;
        if (ifa->ifa_addr->sa_family == AF_INET)
        {
            in_addr_t addr = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr.s_addr;
            in_addr_t mask = ((struct sockaddr_in *)ifa->ifa_netmask)->sin_addr.s_addr;
            if ((addr & mask) != (compare_ip_addr & mask))
                continue;
            info = utils_net_if_info(ifa->ifa_name);
            break;
        }
    }
    freeifaddrs(ifAddrStruct);
    return info;
}

uint8_t utils_net_available(const char *site, uint16_t port, uint8_t read_data, uint8_t timeout_sec)
{
    int sockfd, val;
    uint8_t is_online = 0;
    char buffer[512] = {0};
    struct hostent *ent = NULL;
    struct sockaddr_in addr = {0};
    const char *tgt_site = site;
    uint16_t tgt_port = port;

    if (site == NULL)
        tgt_site = "qq.com";
    if (port == 0)
        tgt_port = 80;
    if ((ent = gethostbyname(tgt_site)) == NULL)
        return is_online;
    if ((sockfd = socket(ent->h_addrtype, SOCK_STREAM, IPPROTO_TCP)) == -1)
        return is_online;
    val = 1;
    if (0 == timeout_sec)
        timeout_sec = 10;
    struct timeval timeout = {.tv_sec = timeout_sec, .tv_usec = 0};
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&val, sizeof(val)) == 0 &&
        setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)) == 0 &&
        setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)) == 0 &&
        setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)) == 0)
    {
        addr.sin_family = ent->h_addrtype;
        memcpy(&(addr.sin_addr), ent->h_addr, ent->h_length);
        addr.sin_port = htons(tgt_port);
        if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == 0)
        {
            if (read_data)
            {
                const char *get_header = "GET / HTTP/1.1\r\n\r\n";
                uint16_t len = strlen(get_header);
                if (write(sockfd, get_header, len) >= (len - 1))
                {
                    shutdown(sockfd, SHUT_WR);
                    if (read(sockfd, buffer, sizeof(buffer)) != -1)
                    {
                        is_online = 1;
                    }
                }
            }
            else
                is_online = 1;
        }
    }
    else
        perror("setsockopt failed");
    close(sockfd);
    return is_online;
}